'use strict';

const fs = require('fs');
const path = require('path');

const inquirer = require('inquirer');
const fse = require('fs-extra');
const semver = require('semver');
const userHome = require('user-home');
const { glob }  = require('glob');
const ejs = require('ejs');

const Command = require('@nz-cli/command');
const log = require('@nz-cli/log');
const Package = require('@nz-cli/package');
const { spinnerStart, sleep, execAsync } = require('@nz-cli/utils');

const getProjectTemplate = require('./getProjectTemplate');

const TYPE_PROJECT = 'project';
const TYPE_COMPONENT = 'component';

const TEMPLATE_TYPE_NORMAL = 'normal';
const TEMPLATE_TYPE_CUSTOM = 'custom';

// 命令白名单 - 防止命令被篡改，出现危险命令
const WHITE_COMMAND = ['npm', 'cnpm'];

class InitCommand extends Command {
  init() {
    this.projectName = this._argv[0] || '';
    const cmdOptions = this._argv[1];
    this.force = !!cmdOptions.force;
    log.verbose('init projectName', this.projectName);
    log.verbose('init force', this.force);
  }

  async exec() {
    try {
      // 准备阶段
      const projectInfo = await this.prepare();
      if (projectInfo) {
        // 下载模板
        log.verbose('projectInfo', projectInfo);
        this.projectInfo = projectInfo;
        await this.downloadTemplate();
        // 安装模板
        await this.installTemplate();
      }
    } catch (error) {
      log.error(error.message);
      if (process.env.LOG_LEVEL === 'verbose') {
        console.log(error);
      }
    }
  }

  async installTemplate() {
    if (this.curTemplateInfo) {
      if (!this.curTemplateInfo.type) {
        this.curTemplateInfo.type = TEMPLATE_TYPE_NORMAL;
      }
      if (this.curTemplateInfo.type === TEMPLATE_TYPE_NORMAL) {
        // 标准安装
        await this.installNormalTemplate();
      } else if (this.curTemplateInfo.type === TEMPLATE_TYPE_CUSTOM) {
        // 自定义安装
        await this.installCustomTemplate();
      } else {
        throw new Error('项目模板类型无法识别!');
      }
    } else {
      throw new Error('项目模板信息不存在!');
    }
  }

  // 检查命令白名单
  checkCommand(cmd) {
    if (WHITE_COMMAND.includes(cmd)) {
      return cmd;
    }
    return null;
  }

  // 安装标准模板-启动子进程执行命令 安装依赖、启动项目
  async execCommand(command, errMsg) {
    let res;
    if (command) {
      const cmdArray = command.split(' ');
      const cmd = this.checkCommand(cmdArray[0]);
      if (!cmd) { throw new Error('命令不存在! 命令：' + command); }
      const args = cmdArray.slice(1);
      res = await execAsync(cmd, args, {
        // 将 stdio流 传给主进程，就不用监听&打印 子进程 的执行信息了
        stdio: 'inherit',
        cwd: process.cwd()
      });
    }
    if (res !== 0) { throw new Error(errMsg); }
    return res;
  }

  // ejs模板渲染
  ejsRender(options) {
    const dir = process.cwd();
    return new Promise(async (resolve, reject) => {
      try {
        // 获取工程目录下的所有文件名称
        const files = await glob('**', {
          cwd: dir,
          ignore: options.ignore || '',
          nodir: true
        });
        // 遍历所有文件进行 ejs 渲染
        Promise.all(files.map(file => {
          // 获取到所有文件的路径
          const filePath = path.join(dir, file);
          return new Promise((resolve1, reject1) => {
            // ejs 渲染动态注入变量
            ejs.renderFile(filePath, this.projectInfo, (err, res) => {
              if (err) {
                reject1(err);
              } else {
                // 将渲染结果回写到文件
                fse.writeFileSync(filePath, res);
                resolve1(res);
              }
            })
          })
        })).then(() => {
          resolve();
        }).catch(err => {
          reject(err);
        })
      } catch (error) {
        reject(error);
      }
    });
  }

  // 安装标准模板
  async installNormalTemplate() {
    // 【1】拷贝模板代码到当前目录
    const spinner = spinnerStart('正在安装模板');
    await sleep();
    try {
      const templatePath = path.resolve(this.templateNpm.cacheFilePath, 'template');
      const targetPath = process.cwd();
      // 确保文件夹存在，不存在的话会自动创建
      fse.ensureDirSync(templatePath);
      fse.ensureDirSync(targetPath);
      fse.copySync(templatePath, targetPath);
    } catch (error) {
      throw error;
    } finally {
      spinner.stop(true);
      log.success('模板安装成功');
    }
    const templateIgnore = this.curTemplateInfo.ignore || [];
    const ignore = ['**/node_modules/**', ...templateIgnore];
    // 【2】ejs 渲染
    await this.ejsRender({ ignore });
    // 【3】依赖安装
    const { installCommand, startCommand } = this.curTemplateInfo;
    await this.execCommand(installCommand, 'npm install 依赖安装过程失败!');
    // 【4】执行启动命令
    await this.execCommand(startCommand, '项目启动失败!');
  }

  // 安装自定义模板
  async installCustomTemplate() {
    // 查询自定义模板的入口文件
    if (await this.templateNpm.exists()) {
      const rootFile = await this.templateNpm.getRootFilePath();
      // 判断入口文件是否存在
      if (fs.existsSync(rootFile)) {
        log.notice('开始执行自定义模板...');
        const templatePath = path.resolve(this.templateNpm.cacheFilePath, 'template');
        const options = {
          curTemplateInfo: this.curTemplateInfo,  // 数据库的模板信息
          projectInfo: this.projectInfo,  // 用户输入的信息
          sourcePath: templatePath, // 缓存路径
          targetPath: process.cwd()
        };
        const code = `require('${rootFile}')(${JSON.stringify(options)})`;
        await execAsync('node', ['-e', code], {
          // 将 stdio流 传给主进程，就不用监听&打印 子进程 的执行信息了
          stdio: 'inherit',
          cwd: process.cwd()
        });
        log.success('自定义模板安装成功! ');
      } else {
        throw new Error('自定义模板入口文件不存在!');
      }
    }
  }

  // 下载模板到用户的缓存目录
  async downloadTemplate() {
    const { projectTemplate } = this.projectInfo;
    const templateInfo = this.templateInfo.find(item => item.npmName === projectTemplate);
    const targetPath = path.resolve(userHome, '.nz-cli', 'template');
    const storeDir = path.resolve(userHome, '.nz-cli', 'template', 'node_modules');
    const { npmName, version } = templateInfo;
    this.curTemplateInfo = templateInfo;
    log.verbose('curTemplateInfo', this.curTemplateInfo);
    const templateNpm = new Package({
      targetPath,
      storeDir,
      packageName: npmName,
      packageVersion: version
    });
    if (! await templateNpm.exists()) {
      const spinner = spinnerStart('正在下载模板');
      await sleep();
      try {
        await templateNpm.install(); 
        spinner.stop(true);
        log.success('下载模板成功');
        this.templateNpm = templateNpm;
      } catch (error) {
        spinner.stop(true);
        throw error;
      }
    } else {
      const spinner = spinnerStart('正在更新模板');
      await sleep();
      try {
        await templateNpm.update(); 
        spinner.stop(true);
        log.success('更新模板成功');
        this.templateNpm = templateNpm;
      } catch (error) {
        spinner.stop(true);
        throw error;
      }
    }
  }

  async prepare(){
    // 判断项目模板是否存在
    const templateInfo = await getProjectTemplate();
    if (!templateInfo || templateInfo.length === 0) {
      throw new Error('项目模板不存在');
    }
    this.templateInfo = templateInfo;
    const localPath = process.cwd();
    if (!this.isDirEmpty(localPath)) {
      // 询问是否继续创建
      const prompt = inquirer.createPromptModule();
      let ifContinueFlag = false;
      if (!this.force) {
        const { ifContinue } = await prompt({
          type: 'confirm',
          name: 'ifContinue',
          default: false,
          message: '当前文件夹不为空，是否继续创建项目？'
        })
        ifContinueFlag = ifContinue;
        if (!ifContinueFlag) { return; }
      }
      // 二次确认
      if (ifContinueFlag || this.force) {
        const { confirmDelete } = await prompt({
          type: 'confirm',
          name: 'confirmDelete',
          default: false,
          message: '是否确认清空【当前目录】下的文件？将会永久删除！请再次检查！'
        })
        if (confirmDelete) {
          // 清空文件夹中的文件
          fse.emptyDirSync(localPath);
        }
      }
    }
    return this.getProjectInfo();
  }

  async getProjectInfo() {
    const isValidName = name => {
      // \w  ==>  a-zA-Z0-9_  
      return /^[a-zA-Z]+[\w-]*[a-zA-Z0-9]$/.test(name);
    }
    let projectInfo = {};
    let isProjectNameValid = false;
    if (isValidName(this.projectName)) {
      isProjectNameValid = true;
      projectInfo.projectName = this.projectName;
    }
    // 选择创建项目 或者 组件
    const prompt = inquirer.createPromptModule();
    const { type } = await prompt({
      type: 'list',
      name: 'type',
      message: '请选择初始化类型',
      default: TYPE_PROJECT,
      choices: [
        { name: '项目', value: TYPE_PROJECT },
        { name: '组件', value: TYPE_COMPONENT }
      ]
    });
    log.verbose('init类型', type);
    // 过滤出 项目模板 或者 组件模板
    this.templateInfo = this.templateInfo.filter(template => {
      return template.tag.includes(type);
    });
    const title = type === TYPE_PROJECT ? '项目' : '组件';
    // 项目名称提示词对象
    const projectNamePrompt =  {
      type: 'input',
      name: 'projectName',
      message: `请输入${title}名称`,
      default: 'nz-cli-app',
      validate: v => {
        const res = isValidName(v);
        if (!res) { return `请输入合法的${title}名称（以为字母开头，中间可以是"字母数字-_"，字母或者数字结尾）` }
        return res;
      },
      filter: v => {
        return v;
      }
    };
    // 创建模板的提示词list
    const templatePrompt = [];
    if (!isProjectNameValid) {
      templatePrompt.push(projectNamePrompt);
    }
    templatePrompt.push(
      {
        type: 'input',
        name: 'projectVersion',
        message: `请输入${title}版本号`,
        default: '1.0.0',
        validate: v => {
          if (!semver.valid(v)) { return '请输入合法的版本号' }
          return !!semver.valid(v);
        },
        filter: v => {
          // 会将 'v1.0.0' 转换为 '1.0.0' ，也会校验合法性
          return semver.valid(v);
        }
      },
      {
        type: 'list',
        name: 'projectTemplate',
        message: `请选择${title}模板`,
        choices: this.createTemplateChoices()
      }
    );
    // 根据用户选择的模板类型，获取交互的返回结果，保存到 projectInfo 并返回
    if (type === TYPE_PROJECT) {
      // 获取 项目 的基本信息
      const project = await prompt(templatePrompt);
      projectInfo = { ...projectInfo, type, ...project };
    } else if (type === TYPE_COMPONENT) {
      // 获取 组件 的基本信息
      const descriptionPrompt = {
        type: 'input',
        name: 'componentDescription',
        message: '请输入组件描述信息',
        default: '',
        validate: v => {
          if (!v) { return '请输入组件描述信息' }
          return true
        }
      }
      templatePrompt.push(descriptionPrompt);
      const component = await prompt(templatePrompt);
      projectInfo = { ...projectInfo, type, ...component };
    }
    if (projectInfo.projectName) {
      projectInfo.name = projectInfo.projectName;
      const kebabCase = await import('kebab-case');
      // 将驼峰形式的改为 "-" 的格式
      projectInfo.className = kebabCase.default(projectInfo.projectName).replace(/^-/, '');
    }
    if (projectInfo.projectVersion) { projectInfo.version = projectInfo.projectVersion; }
    if (projectInfo.componentDescription) { projectInfo.description = projectInfo.componentDescription; }
    return projectInfo;
  }

  // 判断当前目录是否为空
  isDirEmpty(localPath) {
    let fileList = fs.readdirSync(localPath);
    fileList = fileList.filter(file => {
      return !file.startsWith('.') && ['node_modules'].indexOf(file) < 0;
    });
    return !fileList || fileList.length === 0;
  }

  createTemplateChoices() {
    return this.templateInfo.map(item => ({
      value: item.npmName,
      name: item.name
    }));
  }

}

// argv: [projectName, cmdOptions, cmdObj]
function init(argv) {
  return new InitCommand(argv);
}

module.exports = init;
module.exports.InitCommand = InitCommand;
